#!/usr/bin/env python
""" AnyBackup is a windows specific backup program to keep multiple volumes in sync with multiple volumes
    Copyright (C) 2011  Andrew Hopkinson

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    Exported Classes:

    drive -- a core AnyBackup object, contains all meta info about a volume plus a link to the root file
    """

import DriveUtil
from mixins import messagingMixin, pathMixin
import re
import os
import socket
import sys
import datetime
import math
import win32api
import cPickle
from file import file
import Pyro.core
import logging

logger = logging.getLogger('AnyBackup')

class drive(messagingMixin, pathMixin):
    """ a core AnyBackup object, contains all meta info about a volume plus a link to the root file """
    BACKUP=2
    CONTENT=3
    ALL=4
    MISSING=9
    OVERWRITE=10
    port = 60000
    NOTEXIST = 'notexist'
    
    def __init__(self, name, serial, totalSpace, freeSpace, isBackup, setName, paths=None):
        validExt = []
        skipList = []
        rootFile = None
        self._name = name
        self._serial = serial
        self._totalSpace = totalSpace
        self._freeSpace = freeSpace
        self._isBackup = isBackup
        self._setName = setName
        self._validExt = validExt
        self._skipList = skipList
        self._rootFile = rootFile
        self._pendingWrite = 0
        self._pendingWriteDirs = []
        if paths is None:
            self._dirs = [str(os.sep)]
        else:
            self._dirs = paths
        self._locked = False
        self._remoteHost = ''
        self._remotePath = ''
        
    def getSetName(self):
        try:
            return self._setName
        except AttributeError:
            self._setName = 'AnyBackup'
            return self._setName
    
    def setName(self,setName):
        self._setName = setName
        
    def remoteReady(self):
        try:
            if self._remoteHost and self._remotePath:
                return True
            return False
        except AttributeError:
            return False
        
    def owns(self,dir):
        letter = DriveUtil.getLetter(self._name,self._serial)
        if re.search(r'^%s'%letter,dir,re.IGNORECASE):
            if os.path.exists(dir):
                return True
        return False
        
    def getDirList(self):
        try:
            return self._dirs
        except AttributeError:
            self._dirs = [str(os.sep)]
            return self._dirs
        
    def containsDir(self,dir,overwrite=False):
        for directory in self._dirs[:]:
            oldDir = directory
            if re.search(r'^%s'%re.escape(oldDir),dir,re.IGNORECASE):
                return True
            if re.search(r'^%s'%re.escape(dir),oldDir,re.IGNORECASE):
                if overwrite:
                    self.removeDir(oldDir)
                else:
                    return self.OVERWRITE
        return False
            
        
    def addDir(self,dir):
        returnCode = True
        letter = DriveUtil.getLetter(self._name,self._serial)
        newDir = re.sub(r'^%s:'%(letter),'',dir)
        if not re.search(r'%s$'%re.escape(os.sep),newDir,re.IGNORECASE):
                newDir += os.sep
        result = self.containsDir(newDir) 
        if result == self.OVERWRITE:
            self.containsDir(newDir,overwrite=True)
            returnCode = self.OVERWRITE
        elif result:
            return False
        try:
            self._dirs.append(newDir)
        except AttributeError:
            self._dirs = []
            self._dirs.append(newDir)
        return returnCode
            
    def removeDir(self,dir):
        try:
            return self._dirs.remove(dir)
        except AttributeError:
            return False
        except ValueError:
            return False
        
    def getRemoteHost(self):
        return self._remoteHost
    
    def getRemotePath(self):
        return self._remotePath
        
    def setRemoteHost(self,host):
        self._remoteHost = host
        
    def setRemotePath(self,path):
        self._remotePath = path

    def isLocked(self):
        try:
            return self._locked
        except AttributeError:
            return False
    
    def setLocked(self,lockFlag):
        self._locked = lockFlag
        
    def getPendingWrite(self):
        return self._pendingWrite

    def addPendingWrite(self,fileRef):
        if self._pendingWrite+fileRef.getSize() > self._freeSpace*1048576:
            return False
        else:
            self._pendingWriteDirs.append(fileRef.getDir())
            self._pendingWrite += fileRef.getSize()
            return True

    def addPendingDelete(self,fileRef):
        self._pendingWrite -= fileRef.getSize()

    def clearPendingWrite(self):
        self._pendingWriteDirs = []
        self._pendingWrite = 0

    def isBackup(self):
        return self._isBackup

    def setIsBackup(self,isBackup):
        self._isBackup = isBackup

    def getSkipList(self):
        return self._skipList

    def addSkip(self, skip):
        self._skipList.append(skip)

    def setSkipList(self, skipList):
        if skipList:
            self._skipList = skipList

    def getValidExt(self):
        return self._validExt

    def addValidExt(self,ext):
        self._validExt.append(ext)

    def setValidExt(self,validExt):
        if validExt:
            self._validExt = validExt

    def getName(self):
        return self._name

    def setName(self, name):
        self._name = name

    def getSerial(self):
        return self._serial

    def setSerial(self, serial):
        self._serial = serial

    def getTotalSpace(self):
        return self._totalSpace

    def setTotalSpace(self, totalSpace):
        self._totalSpace = totalSpace

    def getFreeSpace(self):
        return self._freeSpace

    def setFreeSpace(self, freeSpace):
        self._freeSpace = freeSpace

    def getRootFile(self):
        return self._rootFile

    def setRootFile(self, rootFile):
        self._rootFile = rootFile

    def addFile(self, file):
        if file:
            self.getRootFile().addFile(file)
            self.updateFreeSpace()
            return True
        return False

    def search(self, regex):
        if self.getRootFile():
            return self.getRootFile().search(regex)
        return None
    
    def remoteIndex(self):
        if not self._remoteHost or not self._remotePath:
            return False
        try:
            commDict = {}
            commDict['sep'] = '\\'
            commDict['path'] = self._remotePath
            commDict['paths'] = self.getDirList()
            commDict['skip'] = self._skipList
            commDict['validext'] = self._validExt
            commDict['name'] = self._name
            commDict['serial'] = self._serial
            server = Pyro.core.getProxyForURI("PYROLOC://%s:%d/indexserver"%(self._remoteHost,self.port))
            files = server.index(commDict)
            if not files:
                return False
            if files == self.NOTEXIST:
                return self.NOTEXIST
            self._rootFile = files
            self.updateFreeSpace()
            return True
        except:
            return False

    def indexDrive(self):
        if self.isConnected():
            self.fileCount = 0
            letter = DriveUtil.getLetter(self._name,self._serial)
            if self.getValidExt():
                validext = '|'.join(self.getValidExt())
                validext = "\.(" + validext + ")$"
            else:
                validext = None
            skip = '|'.join(self.getSkipList())
            if skip:
                skip = re.compile(str(skip),re.IGNORECASE)
            else:
                skip = None
            if self.isBackup():
                self._rootFile = self.index("",letter,validext,skip,root=True)
            else:
                if len(self.getDirList()) > 1 or (len(self.getDirList()) == 1 and self.getDirList()[0] != os.sep):
                    self._rootFile = file('',
                          os.sep,
                          self._name+":",
                          True,
                          0,
                          0,
                          self._name,self._serial)
                    files = []
                    for path in self.getDirList():
                        files.append(self.index(path,letter,validext,skip,root=True))
                    self._rootFile.setFiles(files)
                else:
                    self._rootFile = self.index(self.getDirList()[0],letter,validext,skip,root=True)
            self.updateFreeSpace()
            return True
        else:
            return drive.MISSING
        return False

    def getFileList(self):
        return self.getRootFile().getFileList()

    def index(self,path,letter,validext,skip,root=False):
        if self.isBackup():
            dir = self.createPath(letter+":",self.getSetName(),path)
        else:
            dir = self.createPath(letter+":",path)
        logger.debug('Indexing: {0}'.format(dir.encode('utf-8')))
        while dir.endswith(os.sep):
            dir = dir.rstrip(os.sep)
        if not os.path.isdir(dir):
            logger.warn("Can only call index on a directory!")
            return None
        try:
            files = os.listdir(unicode(dir))
        except:
            return None
        dirObj = file(os.path.basename(dir),
                      os.path.dirname(path),
                      self._name+":",
                      True,
                      0,
                      0,
                      self._name,self._serial)
        passedFiles = []
        if validext:
            #print validext
            ext = re.compile(validext,re.IGNORECASE)
        else:
            ext = None
        for foundFile in files[:]:
            if skip:
                    if re.search(skip,foundFile):
                        continue
            if not os.path.isdir(self.createPath(dir,foundFile)):
                if ext:
                    if not re.search(ext,foundFile):
                        continue
                self.fileCount += 1
                self.sendMessage("Refreshing " + self.getName() + "-" + self.getSerial() + ' - Found %i file(s)... %s'%(self.fileCount,foundFile))
            newFile = None
            if os.path.isdir(self.createPath(dir,foundFile)):
                newFile = self.index(self.createPath(path,foundFile),letter,validext,skip)
            else:
                mtime = int(math.floor(os.path.getmtime(self.fullPath(dir,foundFile))))
                #Hack to reconcile unix's < 1 second time resolution and windows's 2 second resolution
                if mtime%2:
                    mtime += 1
                newFile = file(foundFile,path,self._name+":",False,float(os.path.getsize(self.fullPath(dir,foundFile)))/float(1024),mtime,self._name,self._serial)
            if newFile:
                newFile.setParent(dirObj)
                passedFiles.append(newFile)
        if not passedFiles and not root:
            return None
        dirObj.setFiles(passedFiles)
        #print dirObj.getName()
        return dirObj

    def isConnected(self):
        if self._name is not None and self._serial is not None:
            letter = DriveUtil.getLetter(self._name,self._serial)
            if letter is not None:
                return True
        return False

    def findDir(self,dir):
        if self.getRootFile().findDir(dir):
            return True
        else:
            for pendingDir in self._pendingWriteDirs:
                if pendingDir == dir:
                    return True
        return False

    def updateFreeSpace(self):
        letter = DriveUtil.getLetter(self._name,self._serial)
        if not letter:
            print "Cannot update freespace for " + self._name + " when drive is not connected!\n"
            return False
        freeSpace = DriveUtil.getFreeSpace(letter)
        totalSpace = DriveUtil.getTotalSpace(letter)
        self.setFreeSpace(freeSpace)
        self.setTotalSpace(totalSpace)
        return True

